Skip to content

Allow break and continue in match-arm expression position (B-619) - #3921

Merged
antoniosarosi merged 1 commit into
canaryfrom
antonio/b-619
Jul 6, 2026
Merged

antoniosarosi merged 1 commit into
canaryfrom
antonio/b-619

Conversation

@antoniosarosi

@antoniosarosi antoniosarosi commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

break and continue were rejected in expression position — e.g. a bare match/catch arm value like 0 => break — with E0010 "Expected expression, found break" (and found continue), even though return is accepted there and the braced form 0 => { break; } already works. The return-vs-break asymmetry surfaced during a hackathon app.

Root cause

The expression parser (parse_primary_expr) recognizes return and throw as diverging expressions but had no case for break/continue, so they fell through to the generic "Expected expression" error. Statement position was fine (parse_break_stmt/parse_continue_stmt), which is why the braced arm worked.

Fix — make break/continue symmetric with return

  • Parser: new BREAK_EXPR/CONTINUE_EXPR CST nodes, parsed in parse_primary_expr mirroring RETURN_EXPR. Like parse_return_expr, they do not consume a trailing ; — that stays owned by the enclosing statement.
  • AST lowering: each desugars to a single-statement block ({ break; } / { continue; }), reusing the fully-tested Stmt::Break/Stmt::Continue machinery (divergence-to-never typing, defer replay/unwatch, defer-escape diagnostics). break/continue carry no value, so — unlike return — no dedicated Expr variant is needed, and the braceless form is guaranteed to behave identically to the already-accepted braced arm.
  • Formatter: break/continue arms wrap into a block with a trailing ;, exactly as return arms already do, so output round-trips through BREAK_STMT/CONTINUE_STMT and stays idempotent.

Tests

  • Parser unit test: bare break/continue produce BREAK_EXPR/CONTINUE_EXPR in match arms with no parse errors.
  • Formatter test: braceless jump arms wrap into blocks idempotently.
  • New baml_src/ns_match_arm_break_continue end-to-end fixture whose test blocks execute the loop-control semantics (break exits the loop, continue re-enters the condition) in both statement and value arm positions.

Fixes B-619

Summary by CodeRabbit

  • New Features

    • Added support for break and continue in expression position, including match/catch arms.
    • Improved formatting support for braceless jump arms by wrapping them into block arms as needed.
  • Bug Fixes

    • Fixed expression-context parsing and lowering so break/continue use proper jump semantics instead of recovery behavior.
  • Tests

    • Added regression tests covering break/continue in match-arm expression bodies and end-to-end behavior in loop/value scenarios.

@linear

linear Bot commented Jul 6, 2026

Copy link
Copy Markdown

B-619

@vercel

vercel Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jul 6, 2026 9:34pm
promptfiddle Ready Ready Preview, Comment Jul 6, 2026 9:34pm
promptfiddle2 Ready Ready Preview, Comment Jul 6, 2026 9:34pm

Request Review

@vercel
vercel Bot temporarily deployed to Preview – beps July 6, 2026 16:00 Inactive
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 715c34e6-6862-4de6-bc7f-63a21a6e1af9

📥 Commits

Reviewing files that changed from the base of the PR and between 1d62105 and fcbb6f4.

⛔ Files ignored due to path filters (2)
  • baml_language/crates/baml_tests/snapshots/baml_src/_root.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/match_arm_break_continue.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs
  • baml_language/crates/baml_fmt/src/ast/expressions.rs
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_tests/baml_src/ns_match_arm_break_continue/match_arm_break_continue.baml
🚧 Files skipped from review as they are similar to previous changes (6)
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs
  • baml_language/crates/baml_tests/baml_src/ns_match_arm_break_continue/match_arm_break_continue.baml
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_fmt/src/ast/expressions.rs

📝 Walkthrough

Walkthrough

This PR adds break and continue as expression-position constructs, with parser, lowering, formatter, and end-to-end test support for braceless match-arm usage.

Changes

Break/Continue as Expressions

Layer / File(s) Summary
Syntax kinds and parser entry points
baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs, baml_language/crates/baml_compiler_parser/src/parser.rs
Adds BREAK_EXPR and CONTINUE_EXPR, parses braceless break/continue into dedicated nodes, routes primary-expression dispatch to new helpers, and includes a regression test.
Lower jump expressions
baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
Expression lowering now recognizes BREAK_EXPR and CONTINUE_EXPR and converts them into block expressions containing Stmt::Break or Stmt::Continue.
Formatter expression handling
baml_language/crates/baml_fmt/src/ast/expressions.rs, baml_language/crates/baml_fmt/src/lib.rs
Adds formatter support for braceless break/continue, including CST mapping, verbatim token anchoring, multi-line printing, wrapped-arm semicolons, and a formatter regression test.
End-to-end BAML test coverage
baml_language/crates/baml_tests/baml_src/ns_match_arm_break_continue/match_arm_break_continue.baml
Adds BAML coverage for braceless break and continue in statement-position and value-position match arms.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Parser
  participant Lowerer
  participant Formatter
  Parser->>Parser: emit BREAK_EXPR / CONTINUE_EXPR
  Parser-->>Lowerer: pass expression node
  Lowerer->>Lowerer: wrap jump stmt in Expr::Block
  Lowerer-->>Formatter: Expression::Break / Expression::Continue
  Formatter->>Formatter: print wrapped arm body with semicolon
Loading

Possibly related PRs

  • BoundaryML/baml#3474: Also changes match-arm control-flow for jump constructs, with related work around break/continue behavior in match arms.

Poem

A bunny hopped through match arms new,
With break and continue in view.
No braces? Still okay,
The printer found the way,
And tests said: hop, hop, all true. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enabling break and continue in match-arm expression position.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch antonio/b-619

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 6, 2026 16:06 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
baml_language/crates/baml_fmt/src/ast/expressions.rs (1)

1811-1817: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated braceless-jump check into a helper.

The same matches!(&self.body, Expression::Return(_) | Expression::Break(_) | Expression::Continue(_)) check is duplicated three times (MatchArm twice, CatchArm once). A small helper, e.g. Expression::needs_stmt_semicolon(&self) -> bool, would avoid the triple duplication and reduce risk of the checks drifting apart if a fourth jump kind is added later.

♻️ Proposed refactor
+impl Expression {
+    /// Whether this braceless jump (`return`/`break`/`continue`) requires an
+    /// explicit `;` when wrapped into a block by an arm printer.
+    pub(crate) fn needs_stmt_semicolon(&self) -> bool {
+        matches!(
+            self,
+            Expression::Return(_) | Expression::Break(_) | Expression::Continue(_)
+        )
+    }
+}

Then replace each of the three matches! call sites with self.body.needs_stmt_semicolon().

Also applies to: 1880-1886, 2283-2289

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_fmt/src/ast/expressions.rs` around lines 1811 -
1817, The braceless-jump semicolon check is duplicated across MatchArm and
CatchArm, so extract it into a shared helper on Expression such as
needs_stmt_semicolon() that encapsulates the Return/Break/Continue match. Then
replace each repeated matches! call site in MatchArm and CatchArm with the new
helper so the logic stays consistent if jump kinds change later.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@baml_language/crates/baml_fmt/src/ast/expressions.rs`:
- Around line 1811-1817: The braceless-jump semicolon check is duplicated across
MatchArm and CatchArm, so extract it into a shared helper on Expression such as
needs_stmt_semicolon() that encapsulates the Return/Break/Continue match. Then
replace each repeated matches! call site in MatchArm and CatchArm with the new
helper so the logic stays consistent if jump kinds change later.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e643a277-2333-4d00-8bda-0b79327fe617

📥 Commits

Reviewing files that changed from the base of the PR and between ee295a9 and ee52095.

⛔ Files ignored due to path filters (2)
  • baml_language/crates/baml_tests/snapshots/baml_src/_root.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/match_arm_break_continue.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs
  • baml_language/crates/baml_fmt/src/ast/expressions.rs
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_tests/baml_src/ns_match_arm_break_continue/match_arm_break_continue.baml

@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

Binary size checks passed

✅ 7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
✅ baml-cli Linux 🔒 21.6 MB 9.2 MB file 21.5 MB +78.0 KB (+0.4%) OK
✅ packed-program Linux 🔒 15.8 MB 6.6 MB file 15.6 MB +133.5 KB (+0.9%) OK
✅ baml-cli macOS 🔒 16.6 MB 8.0 MB file 16.6 MB +66.3 KB (+0.4%) OK
✅ packed-program macOS 🔒 12.2 MB 5.8 MB file 12.1 MB +99.6 KB (+0.8%) OK
✅ baml-cli Windows 🔒 18.2 MB 8.2 MB file 18.1 MB +61.4 KB (+0.3%) OK
✅ packed-program Windows 🔒 13.1 MB 5.9 MB file 13.0 MB +104.8 KB (+0.8%) OK
✅ bridge_wasm WASM 14.5 MB 🔒 4.1 MB gzip 4.1 MB +53.6 KB (+1.3%) OK

🔒 = the size this artifact is GATED on (ceiling + delta). Binaries gate on file size (installed binary); WASM gates on gzip (download size). The other size is shown for information only.


Generated by cargo size-gate · workflow run

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 6, 2026 16:18 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps July 6, 2026 19:58 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 6, 2026 20:04 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
baml_language/crates/baml_fmt/src/ast/expressions.rs (1)

69-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Doc comment doesn't mention new Break/Continue variants.

The VerbatimSpan doc comment at Lines 69-73 still only calls out Expression::Unknown and Expression::Return as the variants it backs, even though Expression::Break/Expression::Continue are now also backed by it (Lines 62, 65).

📝 Suggested doc tweak
-/// last *token* ranges. The trivia classifier keys leading/trailing comments to
-/// individual token ranges, so [`Printable::leftmost_token`] /
-/// [`Printable::rightmost_token`] must return those exact token ranges for a
-/// comment to attach and emit. A whole-node span never matches a token key, so
-/// a trailing comment on the node was silently dropped — the `defer` statement
-/// comment-loss bug (B-629), and the same class of bug for a braceless `return`
-/// arm. A whole-node span can also begin inside leading trivia (the parser
+/// last *token* ranges. The trivia classifier keys leading/trailing comments to
+/// individual token ranges, so [`Printable::leftmost_token`] /
+/// [`Printable::rightmost_token`] must return those exact token ranges for a
+/// comment to attach and emit. A whole-node span never matches a token key, so
+/// a trailing comment on the node was silently dropped — the `defer` statement
+/// comment-loss bug (B-629), and the same class of bug for a braceless
+/// `return`/`break`/`continue` arm. A whole-node span can also begin inside leading trivia (the parser
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_fmt/src/ast/expressions.rs` around lines 69 - 84,
Update the `VerbatimSpan` doc comment in `baml_fmt::ast::expressions` so it also
mentions the new `Expression::Break` and `Expression::Continue` variants
alongside `Expression::Unknown` and `Expression::Return`; keep the explanation
about token-range-based printing and leading/trailing trivia handling, but make
the list of backed variants complete so it matches the current `Expression`
enum.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@baml_language/crates/baml_fmt/src/ast/expressions.rs`:
- Around line 69-84: Update the `VerbatimSpan` doc comment in
`baml_fmt::ast::expressions` so it also mentions the new `Expression::Break` and
`Expression::Continue` variants alongside `Expression::Unknown` and
`Expression::Return`; keep the explanation about token-range-based printing and
leading/trailing trivia handling, but make the list of backed variants complete
so it matches the current `Expression` enum.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1f35fe7b-2ef2-4a72-97c0-03aebc38e433

📥 Commits

Reviewing files that changed from the base of the PR and between ee52095 and 1d62105.

⛔ Files ignored due to path filters (2)
  • baml_language/crates/baml_tests/snapshots/baml_src/_root.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/match_arm_break_continue.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs
  • baml_language/crates/baml_fmt/src/ast/expressions.rs
  • baml_language/crates/baml_fmt/src/lib.rs
  • baml_language/crates/baml_tests/baml_src/ns_match_arm_break_continue/match_arm_break_continue.baml
🚧 Files skipped from review as they are similar to previous changes (5)
  • baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_tests/baml_src/ns_match_arm_break_continue/match_arm_break_continue.baml
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_fmt/src/lib.rs

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 6, 2026 20:16 Inactive
`break`/`continue` were rejected in expression position (e.g. a bare
`match`/`catch` arm value like `0 => break`) with E0010 "Expected
expression, found break", even though `return` is accepted there and the
braced form `0 => { break; }` already works.

Root cause: the expression parser (`parse_primary_expr`) recognizes
`return` and `throw` as diverging expressions but had no case for
`break`/`continue`, so they fell through to the generic "Expected
expression" error. Statement position was fine, which is why the braced
arm worked.

Fix — make break/continue symmetric with return:
- Parser: new BREAK_EXPR/CONTINUE_EXPR CST nodes, parsed in
  `parse_primary_expr` mirroring RETURN_EXPR; like `parse_return_expr`
  they don't consume a trailing `;` (that stays owned by the enclosing
  statement).
- AST lowering: each desugars to a single-statement block
  (`{ break; }` / `{ continue; }`), reusing the tested
  Stmt::Break/Stmt::Continue machinery (divergence-to-never typing,
  defer replay/unwatch, defer-escape diagnostics). They carry no value,
  so no dedicated Expr variant is needed and the braceless form behaves
  identically to the already-accepted braced arm.
- Formatter: break/continue arms wrap into a block with a trailing `;`,
  exactly as return arms do, so output round-trips through
  BREAK_STMT/CONTINUE_STMT and stays idempotent.

Tests: parser unit test for BREAK_EXPR/CONTINUE_EXPR in match arms; a
formatter idempotency test; and a new baml_src/ns_match_arm_break_continue
end-to-end fixture whose test blocks execute the loop-control semantics in
both statement and value arm positions.

Fixes B-619
@vercel
vercel Bot temporarily deployed to Preview – beps July 6, 2026 21:16 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 6, 2026 21:22 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 6, 2026 21:34 Inactive
@antoniosarosi
antoniosarosi added this pull request to the merge queue Jul 6, 2026
Merged via the queue into canary with commit 8b47762 Jul 6, 2026
52 checks passed
@antoniosarosi
antoniosarosi deleted the antonio/b-619 branch July 6, 2026 22:16

This branch was previously deployed

3 inactive deployments
Preview – promptfiddle — fcbb6f45 Deployed Jul 6, 2026 by vercel[bot]
Preview – promptfiddle2 — fcbb6f45 Deployed Jul 6, 2026 by vercel[bot]
Preview – beps — fcbb6f45 Deployed Jul 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant